#include "wm_internal_flash.h"

FLASH_ProcessTypeDef pFlash;
static unsigned int getFlashDensity(void);

static vu32 read_first_value(void)
{
    return M32(RSA_BASE);
}

static void writeEnable(void)
{
	FLASH->CMD_INFO = 0x6;
	FLASH->CMD_START = FLASH_CMD_START_CMD;
}

static unsigned char readRID(void)
{
	FLASH->CMD_INFO = 0x2c09F;
	FLASH->CMD_START = FLASH_CMD_START_CMD;
    return read_first_value() & 0xFF;
}
#if 0
static void writeBpBit_for_1wreg(char cmp, char bp4, char bp3, char bp2, char bp1, char bp0)
{
    int status = 0;
    int bpstatus = 0;

    FLASH->CMD_INFO = 0x0C005;
    FLASH->CMD_START = FLASH_CMD_START_CMD;
    status =  read_first_value() & 0xFF;

    FLASH->CMD_INFO = 0x0C035;
    FLASH->CMD_START = FLASH_CMD_START_CMD;
    status  |=  (read_first_value() & 0xFF) << 8;

    /*Write Enable*/
    FLASH->CMD_INFO = 0x6;
    FLASH->CMD_START = FLASH_CMD_START_CMD;

    bpstatus  = (bp4 << 6) | (bp3 << 5) | (bp2 << 4) | (bp1 << 3) | (bp0 << 2);
    status      = (status & 0xBF83) | bpstatus | (cmp << 14);

    M32(RSA_BASE)  = status;
    FLASH->CMD_INFO = 0x1A001;
    FLASH->CMD_START = FLASH_CMD_START_CMD;
}

static void writeBpBit_for_2wreg(char cmp, char bp4, char bp3, char bp2, char bp1, char bp0)
{
    int status = 0;
    int bpstatus = 0;

    FLASH->CMD_INFO = 0x0C005;
    FLASH->CMD_START = FLASH_CMD_START_CMD;
    status =  read_first_value() & 0xFF;

    FLASH->CMD_INFO = 0x0C035;
    FLASH->CMD_START = FLASH_CMD_START_CMD;
    status  |=  (read_first_value() & 0xFF) << 8;

    /*Write Enable*/
    FLASH->CMD_INFO = 0x6;
    FLASH->CMD_START = FLASH_CMD_START_CMD;

    bpstatus  = (bp4 << 6) | (bp3 << 5) | (bp2 << 4) | (bp1 << 3) | (bp0 << 2);
    bpstatus      = (status & 0x83) | bpstatus;

    M32(RSA_BASE)  = bpstatus;
    FLASH->CMD_INFO = 0xA001;
    FLASH->CMD_START = FLASH_CMD_START_CMD;


    FLASH->CMD_INFO = 0x6;
    FLASH->CMD_START = FLASH_CMD_START_CMD;

    status      = ((status>>8) & 0xBF) | (cmp << 6);
    M32(RSA_BASE)   = status;
    FLASH->CMD_INFO  = 0xA031;
    FLASH->CMD_START = FLASH_CMD_START_CMD;	
}


static void writeESMTBpBit(char cmp, char bp4, char bp3, char bp2, char bp1, char bp0)
{
    int status = 0;
    int bpstatus = 0;

    FLASH->CMD_INFO = 0x0C005;
    FLASH->CMD_START = FLASH_CMD_START_CMD;
    status =  read_first_value() & 0xFF;
    bpstatus  = (bp4 << 6) | (bp3 << 5) | (bp2 << 4) | (bp1 << 3) | (bp0 << 2);
    status      = (status & 0x83) | bpstatus;

    /*Write Enable*/
    FLASH->CMD_INFO = 0x6;
    FLASH->CMD_START = FLASH_CMD_START_CMD;

    bpstatus  = (bp4 << 6) | (bp3 << 5) | (bp2 << 4) | (bp1 << 3) | (bp0 << 2);
    status      = (status & 0x83) | bpstatus | (cmp << 14);

    M32(RSA_BASE)  = status;
    FLASH->CMD_INFO = 0x0A001;
    FLASH->CMD_START = FLASH_CMD_START_CMD;


    FLASH->CMD_INFO = 0x0C085;
    FLASH->CMD_START = FLASH_CMD_START_CMD;
    status  =  read_first_value() & 0xFF;

    /*Write Enable*/
    FLASH->CMD_INFO = 0x6;
    FLASH->CMD_START = FLASH_CMD_START_CMD;

    status		= (status & 0xBF) | (cmp << 6);
    M32(RSA_BASE)  = status;
    FLASH->CMD_INFO = 0x0A0C1;
    FLASH->CMD_START = FLASH_CMD_START_CMD;
}

static int flashunlock(void)
{
    switch(readRID())
    {
    case SPIFLASH_MID_GD:
	case SPIFLASH_MID_TSINGTENG:
	case SPIFLASH_MID_TSINGTENG_1MB:
        writeBpBit_for_1wreg(0, 0, 0, 0, 0, 0);
        break;
    case SPIFLASH_MID_PUYA:
		if (getFlashDensity() == 0x100000) /* PUYA 1M Flash use 1 register to set lock/unlock*/
		{
			writeBpBit_for_1wreg(0, 0, 0, 0, 0, 0);
		}
		else
		{
			writeBpBit_for_2wreg(0, 0, 0, 0, 0, 0);
		}
	case SPIFLASH_MID_XTX:
	case SPIFLASH_MID_BOYA:
	case SPIFLASH_MID_FUDANMICRO:
	case SPIFLASH_MID_XMC:
		writeBpBit_for_2wreg(0, 0, 0, 0, 0, 0);
		break;
    case SPIFLASH_MID_ESMT:
        writeESMTBpBit(0, 0, 0, 0, 0, 0);
        break;
    default:
        return -1;
    }
    return 0;
}

static int flashlock(void)
{
    switch(readRID())
    {
    case SPIFLASH_MID_GD:
	case SPIFLASH_MID_TSINGTENG:
	case SPIFLASH_MID_TSINGTENG_1MB:
        writeBpBit_for_1wreg(0, 1, 1, 0, 1, 0);
		break;
    case SPIFLASH_MID_PUYA:
		if (getFlashDensity() == 0x100000) /* PUYA 1M Flash use 1 register to set lock/unlock*/
		{
			writeBpBit_for_1wreg(0, 1, 1, 0, 1, 0);
		}
		else
		{
			writeBpBit_for_2wreg(0, 1, 1, 0, 1, 0);
		}
	case SPIFLASH_MID_XTX:
	case SPIFLASH_MID_BOYA:
	case SPIFLASH_MID_FUDANMICRO:
	case SPIFLASH_MID_XMC:
		writeBpBit_for_2wreg(0, 1, 1, 0, 1, 0);
        break;
    case SPIFLASH_MID_ESMT:
        writeESMTBpBit(0, 1, 1, 0, 1, 0);
        break;
    default:
        return -1;/*do not clear QIO Mode*/
    }
    return 0;
}
#endif
static void writeLbBit_for_1wreg(uint32_t val)
{
	int status = 0;
	
	FLASH->CMD_INFO = 0x0C005;
	FLASH->CMD_START = FLASH_CMD_START_CMD;
	status = read_first_value() & 0xFF;
	
	FLASH->CMD_INFO = 0x0C035;
	FLASH->CMD_START = FLASH_CMD_START_CMD;
	status |= (read_first_value() & 0xFF) << 8;
	
	// Write Enable
	FLASH->CMD_INFO = 0x6;
	FLASH->CMD_START = FLASH_CMD_START_CMD;
	
	status |= (val);
	
	M32(RSA_BASE) = status;
	FLASH->CMD_INFO = 0x1A001;
	FLASH->CMD_START = FLASH_CMD_START_CMD;
}

static void writeLbBit_for_2wreg(uint32_t val)
{
	int status = 0;
	
	FLASH->CMD_INFO = 0x0C005;
	FLASH->CMD_START = FLASH_CMD_START_CMD;
	status = read_first_value() & 0xFF;
	
	FLASH->CMD_INFO = 0x0C035;
	FLASH->CMD_START = FLASH_CMD_START_CMD;
	status |= (read_first_value() & 0xFF) << 8;
	
	// Write Enable
	FLASH->CMD_INFO = 0x6;
	FLASH->CMD_START = FLASH_CMD_START_CMD;
	
	status |= (val);
	status = (status>>8);
	M32(RSA_BASE) = status;
	FLASH->CMD_INFO = 0xA031;
	FLASH->CMD_START = FLASH_CMD_START_CMD;
}

static int programSR(unsigned int  cmd, unsigned long addr, unsigned char *buf,  unsigned int sz)
{
    unsigned long base_addr = 0;
    unsigned int size = 0;


    if (sz > INSIDE_FLS_PAGE_SIZE)
    {
        sz = INSIDE_FLS_PAGE_SIZE;
    }

    base_addr = RSA_BASE;
    size = sz;
    while(size)
    {
        M32(base_addr) = *((unsigned long *)buf);
        base_addr += 4;
        buf += 4;
        size -= 4;
    }

    writeEnable();
    FLASH->CMD_INFO = cmd | ((sz - 1) << 16);
    FLASH->ADDR = (addr & 0x1FFFFFF);
    FLASH->CMD_START = FLASH_CMD_START_CMD;

    return 0;
}


static int programPage (unsigned long adr, unsigned long sz, unsigned char *buf)
{
    programSR(0x80009002, adr, buf, sz);
    return(0);
}

static int eraseSR(unsigned int cmd, unsigned long addr)
{
    /*Write Enable*/
    writeEnable();
    FLASH->CMD_INFO = cmd;
    FLASH->ADDR = (addr & 0x1FFFFFF);
    FLASH->CMD_START = FLASH_CMD_START_CMD;

    return 0;
}

static int eraseSector (unsigned long adr)
{
    eraseSR(0x80000820, adr);

    return (0);                                  				// Finished without Errors
}

static unsigned int getFlashDensity(void)
{
    unsigned char density = 0;

    FLASH->CMD_INFO = 0x2c09F;
    FLASH->CMD_START = FLASH_CMD_START_CMD;

    density = ((read_first_value() & 0xFFFFFF) >> 16) & 0xFF;
    //	printf("density %x\n", density);
    if (density && (density <= 0x21))  /*just limit to (1<<33UL) Byte*/
    {
        return (1 << density);
    }

    return 0;
}

static int __readByCMD(unsigned char cmd, unsigned long addr, unsigned char *buf, unsigned long sz)
{
    int i = 0;
    int word = sz / 4;
    int byte = sz % 4;
    unsigned long addr_read;
	if (!(FLASH->FLASH_CR&0x1))/*non-QIO mode, only single line command can be used*/
	{
		if (cmd > 0x0B)
		{
			cmd = 0x0B;
		}
	}
	
    switch (cmd)
    {
    case 0x03:
        FLASH->CMD_INFO = 0x8000C003 | (((sz - 1) & 0x3FF) << 16);
        FLASH->ADDR = addr & 0x1FFFFFF;
        FLASH->CMD_START = FLASH_CMD_START_CMD;
        break;
    case 0x0B:
        if((FLASH->FLASH_CR & 0x2) == 0x2)
        {
            FLASH->CMD_INFO = 0xB400C00B | (((sz - 1) & 0x3FF) << 16);
        }
        else
        {
            FLASH->CMD_INFO = 0xBC00C00B | (((sz - 1) & 0x3FF) << 16);
        }
        FLASH->ADDR = addr & 0x1FFFFFF;
        FLASH->CMD_START = FLASH_CMD_START_CMD;
        break;
    case 0xBB:
        FLASH->CMD_INFO = 0xE400C0BB | (((sz - 1) & 0x3FF) << 16);
        FLASH->ADDR = addr & 0x1FFFFFF;
        FLASH->CMD_START = FLASH_CMD_START_CMD;
        break;

    case 0xEB:
        FLASH->CMD_INFO = 0xEC00C0EB | (((sz - 1) & 0x3FF) << 16);
        FLASH->ADDR = addr & 0x1FFFFFF;
        FLASH->CMD_START = FLASH_CMD_START_CMD;
        break;


    default:
        return -1;
    }

    //	printf("delay delay delay delay\n");
    //	dumpUint32("readByCMD RSA_BASE", RSA_BASE, sz/4);
    addr_read = RSA_BASE;
    for(i = 0; i < word; i ++)
    {
        M32(buf) = M32(addr_read);
        buf += 4;
        addr_read += 4;
    }

    if(byte > 0)
    {
        M32(buf) = M32(addr_read);
        buf += 3;							//point last byte
        byte = 4 - byte;
        while(byte)
        {
            *buf = 0;
            buf --;
            byte --;
        }
    }
    return 0;
}

static int flashRead(unsigned long addr, unsigned char *buf, unsigned long sz)
{
#define INSIDE_FLS_MAX_RD_SIZE (1024)
    unsigned int flash_addr;
    unsigned int sz_pagenum = 0;
    unsigned int sz_remain = 0;
    int i = 0;
    int page_offset = addr & (INSIDE_FLS_PAGE_SIZE - 1);
	uint32_t max_size = 0;
	
	if ((page_offset == 0)
		&& (((uint32_t)buf & 0x3) == 0)
		&& ((sz & 0x3) == 0)) /*use 4-bytes aligned and buf must be 4 times, sz must be 4 times*/
	{
		flash_addr = addr;
		if (sz >= 512)
		{
			max_size = INSIDE_FLS_MAX_RD_SIZE;
		}
		else
		{
			max_size = INSIDE_FLS_PAGE_SIZE;
		}
		sz_pagenum = sz / max_size;
		sz_remain = sz % max_size;
		for (i = 0; i < sz_pagenum; i++)
		{
			__readByCMD(0xEB, flash_addr, buf, max_size);
			buf += max_size;
			flash_addr += max_size;
		}
		if (sz_remain)
		{
			__readByCMD(0xEB, flash_addr, buf, sz_remain);
		}
	}
	else
	{

		char *cache = NULL;
		
		cache = malloc(INSIDE_FLS_PAGE_SIZE);
		if (cache == NULL)
		{
			return HAL_ERROR;
		}
		
		flash_addr = addr & ~(INSIDE_FLS_PAGE_SIZE - 1);
		__readByCMD(0xEB, flash_addr, (unsigned char *)cache, INSIDE_FLS_PAGE_SIZE);
		if (sz > INSIDE_FLS_PAGE_SIZE - page_offset)
		{
			memcpy(buf, cache + page_offset, INSIDE_FLS_PAGE_SIZE - page_offset);
			buf += INSIDE_FLS_PAGE_SIZE - page_offset;
			flash_addr 	+= INSIDE_FLS_PAGE_SIZE;

			sz_pagenum = (sz - (INSIDE_FLS_PAGE_SIZE - page_offset)) / INSIDE_FLS_PAGE_SIZE;
			sz_remain = (sz - (INSIDE_FLS_PAGE_SIZE - page_offset)) % INSIDE_FLS_PAGE_SIZE;
			for (i = 0; i < sz_pagenum; i++)
			{

				__readByCMD(0xEB, flash_addr, (unsigned char *)cache, INSIDE_FLS_PAGE_SIZE);
				memcpy(buf, cache, INSIDE_FLS_PAGE_SIZE);
				buf 		+= INSIDE_FLS_PAGE_SIZE;
				flash_addr 	+= INSIDE_FLS_PAGE_SIZE;
			}

			if (sz_remain)
			{
				__readByCMD(0xEB, flash_addr, (unsigned char *)cache, sz_remain + (4 - sz_remain%4));
				memcpy(buf, cache, sz_remain);
			}
		}
		else
		{
			memcpy(buf, cache + page_offset, sz);
		}
		free(cache);
		
	}

    return 0;
}

/**
 * @brief          This function is used to read data from the flash.
 *
 * @param[in]      addr             is byte offset addr for read from the flash.
 * @param[in]      buf              is user for data buffer of flash read
 * @param[in]      len              is byte length for read.
 *
 * @retval         HAL_OK	    	if read sucsess
 * @retval         HAL_ERROR	    if read fail
 *
 * @note           None
 */
HAL_StatusTypeDef HAL_FLASH_Read(uint32_t addr, uint8_t *buf, uint32_t len)
{
    int err;

    if (((addr & (INSIDE_FLS_BASE_ADDR - 1)) >=  getFlashDensity()) || (len == 0) || (buf == NULL))
    {
        return HAL_ERROR;
    }

    __HAL_LOCK(&pFlash);

    flashRead(addr, buf, len);

    err = HAL_OK;
    __HAL_UNLOCK(&pFlash);
    return err;
}

/**
 * @brief          This function is used to write data to the flash.
 *
 * @param[in]      addr      is byte offset addr for write to the flash
 * @param[in]      buf       is the data buffer want to write to flash
 * @param[in]      len       is the byte length want to write
 *
 * @retval         HAL_OK	           	if write flash success
 * @retval         HAL_ERROR	    	if argument is invalid
 *
 * @note           None
 */
HAL_StatusTypeDef HAL_FLASH_Write(uint32_t addr, uint8_t *buf, uint32_t len)
{
    uint8_t *cache;
    unsigned int secpos;
    unsigned int secoff;
    unsigned int secremain;
    unsigned int i;
    unsigned int offaddr;

    if (((addr & (INSIDE_FLS_BASE_ADDR - 1)) >=  getFlashDensity()) || (len == 0) || (buf == NULL))
    {
        return HAL_ERROR;
    }

    __HAL_LOCK(&pFlash);
	
	cache = malloc(INSIDE_FLS_SECTOR_SIZE);
	if (cache == NULL)
	{
		__HAL_UNLOCK(&pFlash);
		return HAL_ERROR;
	}

    offaddr = addr & (INSIDE_FLS_BASE_ADDR - 1);			
    secpos = offaddr / INSIDE_FLS_SECTOR_SIZE;				
    secoff = (offaddr % INSIDE_FLS_SECTOR_SIZE);			
    secremain = INSIDE_FLS_SECTOR_SIZE - secoff;    
    if(len <= secremain)
    {
        secremain = len;								
    }
    while (1)
    {
        flashRead(secpos * INSIDE_FLS_SECTOR_SIZE, cache, INSIDE_FLS_SECTOR_SIZE);

        eraseSector(secpos * INSIDE_FLS_SECTOR_SIZE);
        for (i = 0; i < secremain; i++) 
        {
            cache[i + secoff] = buf[i];
        }
        for (i = 0; i < (INSIDE_FLS_SECTOR_SIZE / INSIDE_FLS_PAGE_SIZE); i++)
        {
            programPage(secpos * INSIDE_FLS_SECTOR_SIZE + i * INSIDE_FLS_PAGE_SIZE, INSIDE_FLS_PAGE_SIZE, &cache[i * INSIDE_FLS_PAGE_SIZE]);	
        }
        if(len == secremain)
        {
            break;              
        }
        else                    
        {
            secpos++;           
            secoff = 0;         
            buf += secremain;   
            len -= secremain;
            if(len > (INSIDE_FLS_SECTOR_SIZE))
                secremain = INSIDE_FLS_SECTOR_SIZE; 
            else
                secremain = len;					
        }
    }
	free(cache);
    __HAL_UNLOCK(&pFlash);
    return HAL_OK;
}


void HAL_FLASH_Config_Decrypt_Param(uint32_t code_decrypt, uint32_t dbus_decrypt, uint32_t data_decrypt)
{
	MODIFY_REG(FLASH->DECRYPT_CR, (FLASH_DECRYPT_CR_DATADECRYPT | FLASH_DECRYPT_CR_DBUSDECRYPT | FLASH_DECRYPT_CR_CODEDECRYPT), 
					((data_decrypt << FLASH_DECRYPT_CR_DATADECRYPT_Pos) | (dbus_decrypt << FLASH_DECRYPT_CR_DBUSDECRYPT_Pos) | 
					(code_decrypt << FLASH_DECRYPT_CR_CODEDECRYPT_Pos)));
					
}
/**
 * @brief          This function is used to read the unique id of the internal flash.
 *
 * @param[in]      uid       specified the address to save the uid, the length must be 
 * 							 greater than or equals to 18 bytes.
 *
 * @retval         HAL_OK	           	if read uid success
 * @retval         HAL_ERROR	    	if read uid fail
 *
 * @note           None
 */
HAL_StatusTypeDef HAL_FLASH_Read_Unique_ID(uint8_t *uid)
{
	uint8_t rid;
	uint32_t dumy_bytes = 0, uni_bytes = 0, len, word, byte, value = 0;
	uint32_t addr_read = 0, i = 0;
	uint8_t FLASH_BUF[20];
	uint8_t *addr = &FLASH_BUF[0];
	
	memset(uid, 0xFF, 18);
	rid = readRID();
	switch (rid)
	{
		case SPIFLASH_MID_GD:
		case SPIFLASH_MID_PUYA:
		case SPIFLASH_MID_TSINGTENG:
		case SPIFLASH_MID_TSINGTENG_1MB:
		case SPIFLASH_MID_UC:
			dumy_bytes = 4;
			uni_bytes = 16;
			break;
			
		case SPIFLASH_MID_WINBOND:
		case SPIFLASH_MID_FUDANMICRO:
		case SPIFLASH_MID_BOYA:
		case SPIFLASH_MID_XMC:
			dumy_bytes = 4;
			uni_bytes = 8;
			break;
			
		case SPIFLASH_MID_ESMT:
		case SPIFLASH_MID_XTX:
		default:
			return HAL_ERROR;
	}
	
	uid[0] = rid;
	uid[1] = (uint8_t)(uni_bytes & 0xFF);
	len = dumy_bytes + uni_bytes;
	word = len / 4;
	byte = len % 4;
	value = 0xC04B | ((len - 1) << 16);
	FLASH->CMD_INFO = value;
	FLASH->CMD_START = FLASH_CMD_START_CMD;
	
	addr_read = RSA_BASE;
	for (i = 0; i < word; i++)
	{
		M32(addr) = M32(addr_read);
		addr += 4;
		addr_read += 4;
	}
	if (byte > 0)
	{
		M32(addr) = M32(addr_read);
		addr += 3;
		while(byte)
		{
			*addr = 0;
			addr --;
			byte --;
		}
	}
	addr = &FLASH_BUF[0];
	memcpy(&uid[2], addr + dumy_bytes, uni_bytes);
	
	return HAL_OK;
}

void HAL_FLASH_QSPI_Enable(void)
{
	uint8_t rid;
	uint32_t status = 0, status0 = 0;
	
	rid = readRID();
	switch (rid)
	{
		case SPIFLASH_MID_UC:
		case SPIFLASH_MID_WINBOND:
		case SPIFLASH_MID_FUDANMICRO:
		case SPIFLASH_MID_BOYA:
		case SPIFLASH_MID_XMC:
			FLASH->CMD_INFO = 0x0C035;
			FLASH->CMD_START = FLASH_CMD_START_CMD;
			status =  read_first_value() & 0xFF;
			if ((status & (1 << 1)) == 0)
			{
				/* Write Enable */
				FLASH->CMD_INFO = 0x06;
				FLASH->CMD_START = FLASH_CMD_START_CMD;
				
				/* Write QE bit to flash status register */
				M32(RSA_BASE)  = status | (1 << 1);
				FLASH->CMD_INFO = 0x0A031;
				FLASH->CMD_START = FLASH_CMD_START_CMD;
			}
			if ((FLASH->FLASH_CR & 0x01) == 0)
			{
				FLASH->FLASH_CR = 0x0D; // modify flash controller to quad-spi mode.
			}
			break;
		
		case SPIFLASH_MID_PUYA:
		case SPIFLASH_MID_TSINGTENG_1MB:
			if (getFlashDensity() <= (1 << 20))
			{
				FLASH->CMD_INFO = 0x0C035;
				FLASH->CMD_START = FLASH_CMD_START_CMD;
				status =  read_first_value() & 0xFF;
				if ((status & (1 << 1)) == 0)
				{
					/* Write Enable */
					FLASH->CMD_INFO = 0x06;
					FLASH->CMD_START = FLASH_CMD_START_CMD;
					
					/* Read Status 0 */
					FLASH->CMD_INFO = 0x0C005;
					FLASH->CMD_START = FLASH_CMD_START_CMD;
					status0 =  read_first_value() & 0xFF;
				
					/* Write QE bit to flash status register */
					M32(RSA_BASE)  = ((status | (1 << 1)) << 8) | status0;
					FLASH->CMD_INFO = 0x1A001;
					FLASH->CMD_START = FLASH_CMD_START_CMD;
				}
				if ((FLASH->FLASH_CR & 0x01) == 0)
				{
					FLASH->FLASH_CR = 0x0D; // modify flash controller to quad-spi mode.
				}
			}
			else
			{
				FLASH->CMD_INFO = 0x0C035;
				FLASH->CMD_START = FLASH_CMD_START_CMD;
				status =  read_first_value() & 0xFF;
				if ((status & (1 << 1)) == 0)
				{
					/* Write Enable */
					FLASH->CMD_INFO = 0x06;
					FLASH->CMD_START = FLASH_CMD_START_CMD;
					
					/* Write QE bit to flash status register */
					M32(RSA_BASE)  = status | (1 << 1);
					FLASH->CMD_INFO = 0x0A031;
					FLASH->CMD_START = FLASH_CMD_START_CMD;
				}
				if ((FLASH->FLASH_CR & 0x01) == 0)
				{
					FLASH->FLASH_CR = 0x0D; // modify flash controller to quad-spi mode.
				}
			}
			
			break;
			
		case SPIFLASH_MID_TSINGTENG:
		case SPIFLASH_MID_XTX:
			FLASH->CMD_INFO = 0x0C035;
			FLASH->CMD_START = FLASH_CMD_START_CMD;
			status =  read_first_value() & 0xFF;
			if ((status & (1 << 1)) == 0)
			{
				/* Write Enable */
				FLASH->CMD_INFO = 0x06;
				FLASH->CMD_START = FLASH_CMD_START_CMD;
				
				/* Read Status 0 */
				FLASH->CMD_INFO = 0x0C005;
				FLASH->CMD_START = FLASH_CMD_START_CMD;
				status0 =  read_first_value() & 0xFF;
				
				/* Write QE bit to flash status register */
				M32(RSA_BASE)  = ((status | (1 << 1)) << 8) | status0;
				FLASH->CMD_INFO = 0x1A001;
				FLASH->CMD_START = FLASH_CMD_START_CMD;
			}
			if ((FLASH->FLASH_CR & 0x01) == 0)
			{
				FLASH->FLASH_CR = 0x0D; // modify flash controller to quad-spi mode.
			}
			break;
			
		default:
			break;
	}
}

HAL_StatusTypeDef HAL_FLASH_OTP_Read(uint32_t addr, uint8_t *buf, uint32_t len)
{
	int i = 0, word = len / 4, byte = len % 4;
	unsigned long addr_read = 0xBC00c048, addr_offset = 0, sz_need = len;
	volatile unsigned long value;
	
	__HAL_LOCK(&pFlash);
	if (buf)
	{
		addr_offset = addr % 16;
		sz_need = (addr_offset + len + 16) / 16 * 16;
		addr = addr / 16 * 16;
	}
	FLASH->CMD_INFO = addr_read | (((sz_need - 1) & 0x3FF) << 16);
	FLASH->ADDR = (addr & 0x1FFFFFF);
	FLASH->CMD_START |= FLASH_CMD_START_CMD;
	
	if (buf)
	{
		addr_read = RSA_BASE + (addr_offset / 4 * 4);
		i = (4 - addr_offset % 4) % 4;
		if (i > len)
		{
			byte = len;
		}
		else
		{
			byte = i;
		}
		if (byte)
		{
			value = M32(addr_read);
			memcpy(buf, ((char *)&value) + 4 - i, byte);
			addr_read += 4;
			buf += byte;
		}
		word = (len - byte) / 4;
		for (i = 0; i < word; i++)
		{
			value = M32(addr_read);
			memcpy(buf, (char *)&value, 4);
			buf += 4;
			addr_read += 4;
		}
		byte = (len - byte) % 4;
		if (byte > 0)
		{
			value = M32(addr_read);
			memcpy(buf, (char *)&value, byte);
		}
	}
	
	__HAL_UNLOCK(&pFlash);
    return HAL_OK;
}

HAL_StatusTypeDef HAL_FLASH_OTP_Write(uint32_t addr, uint8_t *buf, uint32_t len)
{
	int l = 0;
	uint32_t erasecmd = 0x80000844, writecmd = 0x80009042, eraseAddr = 0;
	uint16_t eraseSize = 0, pageSize = 0;
	uint8_t flashid = 0, *backbuf = NULL, *q = NULL;
	unsigned long size = 0, p = 0;
	
	if (buf == NULL)
	{
		return HAL_ERROR;
	}
	__HAL_LOCK(&pFlash);
	flashid = readRID();
	switch (flashid)
	{
		case SPIFLASH_MID_GD:
		case SPIFLASH_MID_TSINGTENG_1MB:
			eraseSize = 1024;
			break;
		case SPIFLASH_MID_FUDANMICRO:
			eraseSize = 1024;
			if (getFlashDensity() <= (1 << 20))
			{
				eraseSize = 256;
			}
			break;
		case SPIFLASH_MID_TSINGTENG:
		case SPIFLASH_MID_BOYA:
		case SPIFLASH_MID_XMC:
		case SPIFLASH_MID_WINBOND:
			eraseSize = 256;
			break;
		case SPIFLASH_MID_PUYA:
			eraseSize = 512;
			break;
		case SPIFLASH_MID_XTX:
		case SPIFLASH_MID_ESMT:
			eraseSize = 0;
			break;
		default:
			__HAL_UNLOCK(&pFlash);
			return HAL_ERROR;
	}
	pageSize = 256;
	if (eraseSize == 0 || pageSize == 0)
	{
		return HAL_ERROR;
	}
	eraseAddr = addr & ~(eraseSize - 1);
	if(addr < eraseAddr || len > eraseSize - (addr - eraseAddr))
	{
		return HAL_ERROR;
	}
	backbuf = malloc(eraseSize);
	if (backbuf == NULL)
	{
		return HAL_ERROR;
	}
	p = eraseAddr;
	q = backbuf;
	size = eraseSize;
	while (size > 0)
	{
		l = size > pageSize ? pageSize : size;
		if(HAL_FLASH_OTP_Read(p, q, l) != HAL_OK)
		{
			free(backbuf);
			return HAL_ERROR;
		}
		q += l;
		p += l;
		size -= l;
	}
	eraseSR(erasecmd, eraseAddr);
	memcpy(backbuf + (addr - eraseAddr), buf, len);
	p = eraseAddr;
	q = backbuf;
	size = eraseSize;
	while(size > 0)
	{
		l = size > pageSize ? pageSize : size;
		programSR(writecmd, p, q, l);
		q += l;
		p += l;
		size -= l;
	}
	__HAL_UNLOCK(&pFlash);
	free(backbuf);
	
	return HAL_OK;
}

HAL_StatusTypeDef HAL_FLASH_OTP_Lock(void)
{
	uint8_t flashid;
	int ret = HAL_OK;
	
	__HAL_LOCK(&pFlash);
	flashid = readRID();
	switch (flashid)
	{
		case SPIFLASH_MID_GD:
		case SPIFLASH_MID_TSINGTENG:
			writeLbBit_for_1wreg((1<<10));
			break;
		case SPIFLASH_MID_TSINGTENG_1MB:
			writeLbBit_for_1wreg((7<<11));
			break;
		case SPIFLASH_MID_FUDANMICRO:
			writeLbBit_for_2wreg((1<<10));
			break;
		case SPIFLASH_MID_BOYA:
		case SPIFLASH_MID_XMC:
		case SPIFLASH_MID_WINBOND:
		case SPIFLASH_MID_PUYA:
			writeLbBit_for_2wreg((7<<11));
			break;
		case SPIFLASH_MID_XTX:
		case SPIFLASH_MID_ESMT:
		default:
			ret = HAL_ERROR;
	}
	__HAL_UNLOCK(&pFlash);
	return ret;
}

static uint8_t *gFlashCache = NULL;

HAL_StatusTypeDef FlashFastWriteInit(void)
{
	if(gFlashCache != NULL)
	{
		return HAL_OK;
	}
	gFlashCache = malloc(INSIDE_FLS_SECTOR_SIZE);
	if (gFlashCache == NULL)
	{
		return HAL_ERROR;
	}
	return HAL_OK;
}

static void FlashFlushSector(uint32_t sector)
{
	uint32_t i, addr;
	
	if (sector < (getFlashDensity() / INSIDE_FLS_SECTOR_SIZE + INSIDE_FLS_BASE_ADDR / INSIDE_FLS_SECTOR_SIZE))
	{
		addr = sector * INSIDE_FLS_SECTOR_SIZE;
		
		eraseSector(addr);
		for (i = 0; i < (INSIDE_FLS_SECTOR_SIZE / INSIDE_FLS_PAGE_SIZE); i++)
		{
			programPage(sector * INSIDE_FLS_SECTOR_SIZE + i * INSIDE_FLS_PAGE_SIZE, 
						INSIDE_FLS_PAGE_SIZE, &gFlashCache[i * INSIDE_FLS_PAGE_SIZE]);
		}
	}
}

HAL_StatusTypeDef FlashFastWrite(uint32_t addr, uint8_t *buf, uint32_t len)
{
	uint32_t sector, offset, maxlen;
	
	if (((addr & (INSIDE_FLS_BASE_ADDR - 1)) >=  getFlashDensity()) || (len == 0) || (buf == NULL))
    {
        return HAL_ERROR;
    }

    __HAL_LOCK(&pFlash);
	
	sector = addr / INSIDE_FLS_SECTOR_SIZE;
	offset = addr % INSIDE_FLS_SECTOR_SIZE;
	maxlen = INSIDE_FLS_SECTOR_SIZE - offset;
	
	while (len > 0)
	{
		maxlen = (len > maxlen) ? maxlen : len;
		memcpy(gFlashCache + offset, buf, maxlen);
		buf += maxlen;
		len -= maxlen;
		if ((offset + maxlen) >= INSIDE_FLS_SECTOR_SIZE)
		{
			FlashFlushSector(sector);
			sector++;
			offset = 0;
			maxlen = INSIDE_FLS_SECTOR_SIZE;
		}
	}
	__HAL_UNLOCK(&pFlash);
    return HAL_OK;
}

HAL_StatusTypeDef FlashFastWriteDestroy(uint32_t addr)
{
	uint32_t sector, offset;
	
	if ((addr & (INSIDE_FLS_BASE_ADDR - 1)) >=  getFlashDensity())
    {
        return HAL_ERROR;
    }
	__HAL_LOCK(&pFlash);
	sector = addr / INSIDE_FLS_SECTOR_SIZE;
	offset = addr % INSIDE_FLS_SECTOR_SIZE;
	
	if (gFlashCache != NULL)
	{
		if (offset > 0)
		{
			FlashFlushSector(sector);
		}
		free(gFlashCache);
		gFlashCache = NULL;
	}
	__HAL_UNLOCK(&pFlash);
	
	return HAL_OK;
}
